package javango.contrib.hibernate;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;

public class ModelDao<T> {
	private final static Log log = LogFactory.getLog(ModelDao.class);

	private Class<T> modelClass;
	
	protected HibernateUtil hibernateUtil;
	protected ModelDaoHelper daoHelper;
	
	public ModelDao(Class<T> modelClass) {
		this(JavangoModule.injector.getInstance(HibernateUtil.class), modelClass);
	}
    
	public ModelDao(HibernateUtil hibernateUtil, final Class<T> modelClass) {
		this.hibernateUtil = hibernateUtil;
		this.modelClass = modelClass;
		this.daoHelper = new ModelDaoHelper(hibernateUtil, modelClass);
	}	
	
	@SuppressWarnings("unchecked")
	public T get(Serializable pk) throws DaoException {
		if (pk == null) return null;
		
		Class[] keyClass = getPkClass();
		if (keyClass.length != 1) {
			throw new DaoException("get(pk) not supported for composite keyed models");
		}		
		if (pk.getClass().equals(keyClass[0])) {
			return (T) hibernateUtil.getSession().get(modelClass, pk);
		}		
		
		if (log.isDebugEnabled()) log.debug("Trying to convert input to type " + keyClass[0]);
		
		Object o = daoHelper.convert(pk.toString(), keyClass[0]);
		return (T) hibernateUtil.getSession().get(modelClass, (Serializable)o);
	}
	
	public Criteria filter() throws DaoException {
		return hibernateUtil.getSession().createCriteria(modelClass);
	}
	
	public Criteria filter(Object params) throws DaoException {
		Criteria criteria = filter();
		daoHelper.updateCriteria(criteria, params);
		return criteria;
	}

	public Criteria filter(Pagination pagination) throws DaoException {
		return filter(null, pagination);
	}
	
	public Criteria filter(Object params, Pagination pagination) throws DaoException {
		Criteria criteria = filter();
		daoHelper.updateCriteria(criteria, params);
		
		// pagination
		pagination.setHits(count(criteria));
		if (pagination.getFirst_on_page() != null) {
			criteria.setFirstResult(pagination.getFirst_on_page().intValue() < 1 ? 0 : pagination.getFirst_on_page().intValue() - 1);
		}
		if (pagination.getPaginate_by() != null) {
			criteria.setMaxResults(pagination.getPaginate_by().intValue());
		}
		
		return criteria;
	}
	
	public Long count() throws DaoException {
		return count(filter());
	}
	
	public Long count(Criteria c) throws DaoException {
		return new Long(hibernateUtil.doCount(c));
	}
	
	public void save(Object bean) throws DaoException {
		hibernateUtil.getSession().save(bean);
	}

	public void delete(Object bean) throws DaoException {
		hibernateUtil.getSession().delete(bean);
	}
	
	public Object getPk(Object bean) throws DaoException {
		return hibernateUtil.getSession().getIdentifier(bean);
	}
		
	/**
	 * Returns a the keys Class[] 
	 * @return
	 * @throws DaoException
	 */
	public Class[] getPkClass() throws DaoException {
		Configuration cfg = hibernateUtil.getConfiguration();
		PersistentClass pclass = cfg.getClassMapping(modelClass.getName());

		if (pclass == null) {
			throw new DaoException("Unable to find class : "
					+ modelClass.toString());
		}

		Property componentProperty = pclass.getIdentifierProperty();
		if (componentProperty == null) {
			Component component = pclass.getIdentifierMapper();
			if (component == null) {
				throw new DaoException("Unable to get pk mapping");
			}
			if (log.isDebugEnabled()) log.debug(String.format("Found %d keys for model %s", component.getPropertySpan(), modelClass.getName()));

			Class<?>[] classArray = new Class[component.getColumnSpan()];
			Iterator<Property> ite = component.getPropertyIterator();
			int i = 0;
			while (ite.hasNext()) {
				Property p = ite.next();
				if (log.isDebugEnabled()) log.debug("property name: " + p.getName());
				classArray[i++] = p.getType().getReturnedClass();
			}
			return classArray;
		} else {

			Value value = componentProperty.getValue();
			if (value == null)
				throw new DaoException("Component value is null");
			else {
				if (log.isDebugEnabled()) log.debug(String.format("Found simple key for model %s '%s'",
						modelClass.getName(), value.getType()
								.getReturnedClass()));
				return new Class[] { value.getType().getReturnedClass() };
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	public List<T> search(Object params) throws DaoException {		
		return filter(params).list();
	}
	
	@SuppressWarnings("unchecked")
	public List<T> search(Object params, Pagination p) throws DaoException {
		return filter(params, p).list();
	}
}
